/*
 * Purpose: Control and coordinate interaction between UI and program
 */
package Controller;

import Model.Program;
import Model.Programmable;
import Model.SourceCode;
import Util.Execute;
import Util.FileSystem;
import View.MainWindow;
import java.awt.Component;
import java.io.File;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.filechooser.FileFilter;

/**
 *
 * @author jase
 */
public class IDEManager {

	private Program program;
	private MainWindow window;

	public IDEManager(MainWindow w) {
		program = new Program();
		window = w;
	}

	public void shutdown() {
		program.shutdown();
	}

	public boolean projectOpen() {
		return program.getPath() != null;
	}

	// create a new project - assumes that no project is already open
	public void createProject() {
		File dir = chooseDirectory("create");

		if (dir == null) {
			window.showStatus("you cancelled your create project request");
			return;
		}

		if (dir.exists()) {
			window.showStatus("project already exists at that location");
			return;
		}

		boolean okay = setupDirectory(dir);
		if (!okay) {
			window.showStatus("ready...");
			return;
		}

		// show the user that the project is now "open"
		// the project should NOT contain any source code files...
		program.load(dir);
		window.tabsEnabled(false);
		window.fileMenuEnabled(true);
		window.setEditorText(program.getName() + " project opened");
		window.setTitle(program.getName());
	}

// open a project - assumes that no project is already open
	public void openProject() {
		File path = chooseDirectory("open");

		if (path == null) {
			window.showStatus("you cancelled your open project request");
			return;
		}

		if (verifyDirectory(path)) {
			window.tabsEnabled(false);
			window.fileMenuEnabled(true);

			// add tabs for the source code files
			SourceCode[] codes = program.load(path);
			window.setEditorText(program.getName() + " project open");
			window.setTitle(program.getName());
			if (codes == null) {
				return;
			}

			window.tabsEnabled(true);

			for (SourceCode code : codes) {
				System.out.println("source code: " + code.getName());
				Programmable prog = window.addTab(code.getName(), code.getCode());
				program.addFile(code.getName(), prog);
			}

		} else {
			window.showStatus(path.getName() + " is not a H20 project directory!");
		}
	}

// closes the currently open project - assume that a project IS open :)
	public void closeProject() {

		program.clear();
		window.tabsEnabled(false);
		window.fileMenuEnabled(false);
		window.setEditorText("no project open");
		window.setTitle("");
	}

	// delete an existing project - assumes that project is not already open
	public void deleteProject() {
		File dir = chooseDirectory("delete");

		if (dir == null) {
			window.showStatus("you cancelled your delete project request");
			return;
		}

		if (verifyDirectory(dir)) { // found the project marker file
			// delete project
			deleteDirectory(dir);
			program.clear();
			window.showStatus("deleted project: " + dir.getName());
		} else {
			window.showStatus(dir.getName() + " is not a valid H20 project");
		}
	}

	// find the class with the midlet in it
	private File findMainClass() {
		File path = new File(program.getPath(), "src"); // within the source code subdir

		File[] files = path.listFiles(new FilenameFilter() {

			public boolean accept(File dir, String name) {
				System.out.println("name: " + name);
				return name.endsWith(".java");
			}
		});

		int count = 0;
		File mainFile = null;
		System.out.println("count: " + files.length);
		for (File file : files) {
			System.out.println("checking " + file.getName());
			String name = file.getAbsolutePath();
			String text = FileSystem.readAll(name);

			Pattern p = Pattern.compile("extends\\s+MobileApplication");
			Matcher m = p.matcher(text);

			if (m.find()) {
				++count;
				mainFile = file;
			}
//			if (text.indexOf("MobileApplication") != -1) {
//				System.out.println("found!");
//				++count;
//				mainFile = file;
//			}
		}

		if (count == 1) {
			return mainFile;
		}
		return null;
	}

	// this takes the current class files
	// runs preverify, generates the jar and jad files
	// and activates the emulator
	public void runProject() {
		window.showStatus("preparing project...");

		// delete the "old files" in the pre directory
		String projectPath = program.getPath().getAbsolutePath();
		System.out.println("System\\runsilent del /Q /F \"" + projectPath + "\\pre\\*.class\"");
		String result = Execute.process("System\\runsilent del /Q /F \"" + projectPath + "\\pre\\*.class\"");
		System.out.println("result: " + result);


		// preverify project classes - NOTE: H2O classes shold already be in the pre subdir
		// for the project!!

		// update the H2O sub dir in the project's pre subdir
		System.out.println("System\\runsilent xcopy System\\H2O \"" + projectPath + "\\pre\\H2O\" /E /I /Y");
		result = Execute.process("System\\runsilent xcopy System\\H2O \"" + projectPath + "\\pre\\H2O\" /E /I /Y");
		System.out.println("result: " + result);

		window.showStatus("verifying...");

		result = Execute.process("JME\\bin\\preverify -classpath JME\\lib\\cldc_1.0.jar;" +
			"JME\\lib\\midp_1.0.jar;System\\H2O.jar -d \"" + projectPath + "\\pre\" \"" + projectPath + "\\class\"");

		if (result != null && result.length() > 0) {
			window.showStatus("failed to create");
			return;
		}

		// find the main source code class file
		File main = findMainClass();
		if (main == null) {
			window.showStatus("You need to create ONE file that contains a MobileApplication class");
			return;
		}

		// generate the MANIFEST.MF file, based on the name of the main class
		String className = main.getName().split("\\.")[0]; // ignore the ".java"
		System.out.println("class name: " + className);
		File manifest = new File(projectPath, "MANIFEST.MF");
		String manifestInfo = "MIDlet-1: " + className + ", ," + className + "\n" +
			"MIDlet-Name: " + className + "\n" +
			"MIDlet-Vendor: H20\n" +
			"MIDlet-Version: 1.0\n" +
			"MicroEdition-Configuration: CLDC-1.1\n" +
			"MicroEdition-Profile: MIDP-2.0\n";
		FileSystem.writeAll(manifest.getAbsolutePath(), manifestInfo);

		// create the jar file!
		System.out.println("JDK\\bin\\jar cfm" +
			" \"" + projectPath + "\\dist\\" + className + ".jar\"" +
			" \"" + manifest.getAbsolutePath() + "\"" +
			" -C \"" + projectPath + "\\res\" ." +
			" -C \"" + projectPath + "\\pre\" .");

		window.showStatus("building...");

		result = Execute.process("JDK\\bin\\jar cfm" +
			" \"" + projectPath + "\\dist\\" + className + ".jar\"" +
			" \"" + manifest.getAbsolutePath() + "\"" +
			" -C \"" + projectPath + "\\res\" ." +
			" -C \"" + projectPath + "\\pre\" .");
//		result = Execute.process("JDK\\bin\\jar cfm \"" +
//			projectPath + "\\dist\\" + className +
//			".jar\" \"" + manifest.getAbsolutePath() + "\" -C \"" +
//			projectPath + "\\pre\" . -C \"" +
//			projectPath + "\\pic\" .");

		System.out.println("result: " + result);

		if (result != null && result.length() < 1) {
			// get the size of the jar file
			File jarFile = new File(program.getPath(), "dist/" + className + ".jar");
			System.out.println("jar: " + jarFile.getAbsolutePath());
			long size = jarFile.length();

			String text = "MIDlet-1: " + className + ",, " + className + "\n" + "MIDlet-Description: " + className + "\n" + "MIDlet-Jar-Size: " + size + "\n" + "MIDlet-Jar-URL: " + className + ".jar\n" + "MIDlet-Name: " + className + "\n" + "MIDlet-Vendor: H20\n" + "MIDlet-Version: 1.0\n" + "MicroEdition-Configuration: CLDC-1.1\n" + "MicroEdition-Profile: MIDP-2.0";

			File dir = new File(program.getPath(), "dist/" + className + ".jad");
			FileSystem.writeAll(dir.getAbsolutePath(), text);

			// update the emulate.bat file - setting the path based on the current absolute pathname

			String installPath = (new File("")).getAbsolutePath();

			String batchText = "set PATH=" + installPath + "\\JDK\\jre\\bin;" +
				installPath + "\\JME\\bin;%PATH% & " +
				"emulator -Xdescriptor:\"" + dir.getAbsolutePath() + "\"";

			// run it! :)
			window.showStatus("please wait for emulator...and then close it");
			Execute.runSeparate("System\\runsilent " + batchText);
//			Execute.process("System\\runsilent " + batchText);

//			window.showStatus("okay");

		} else {
			window.showStatus("unable to create project executable");
		}

	}

// assuming we have an open project,
// this will create a new source code pane
// and add it to the editorPanel (which will be
// set to tab mode)
	public void createFile() {

		String name = chooseFilename("create");

		if (name == null) {
			window.showStatus("invalid file name");
			return;
		}

		File file = new File(program.getPath(), "/src/" + name + ".java");
		if (file.exists()) {
			window.showStatus(name + " already exists for " + program.getName() + " project");
			return;

		} else if (file.isDirectory()) {
			window.showStatus(name + " is a directory");
			return;

		} else if (file.isHidden()) {
			window.showStatus(name + " is a hidden filename can cant be used");
			return;
		}

		boolean created = false;
		try {
			created = file.createNewFile();
		} catch (IOException e) {
			created = false;
		}

		if (!created) {
			JOptionPane.showMessageDialog(window,
				"unable to create new file, please try again");
			return;
		}

		window.showStatus("created " + name);
		window.tabsEnabled(true);
		Programmable programmable = window.addTab(name);
		program.addFile(name, programmable);
	}

	public void openFile() {
		String name = chooseFilename("open");

		if (name == null) {
			window.showStatus("invalid file name");
			return;
		}

		if (program.hasFile(name)) {
			window.showStatus(name + " is already open!");
			return;
		}

		File file = new File(program.getPath(), name + ".java");
		if (!file.exists()) {
			window.showStatus(name + " is not a file in " + program.getName() + " project");
			return;
		}

		window.showStatus("opened " + name);
		window.tabsEnabled(true);
		Programmable programmable = window.addTab(name);
		program.addFile(name, programmable);
	}

	public void deleteFile() {
		String name = chooseFilename("delete");

		if (name == null) {
			window.showStatus("invalid file name");
			return;
		}

		File file = new File(program.getPath(), "/src/" + name + ".java");

		System.out.println("file path: " + file.getAbsolutePath());

		if (!file.exists()) {
			window.showStatus(name + " is not a file in " + program.getName() + " project");
			return;
		}

		boolean deleted = file.delete();
		if (!deleted) {
			JOptionPane.showMessageDialog(window, "could not delete the file");
			return;
		}


		window.showStatus(name + " deleted");
		program.removeFile(name);
		program.refresh(); // force the rest of the files to recompile
		window.removeTab(name);
	}

	// try and load the userdirectory property from the config file
	// if not able to, then return null
	private static File getUserDir() {

		File dir = null;

		Properties properties = new Properties();

		try {
			// load config file form the current working directory
			File workingDir = new File(System.getProperty("user.dir"));
			properties.load(new FileReader(new File(workingDir, "\\config.txt")));

			String value = properties.getProperty("newProjectDirectory");

			if (value != null) {
				dir = new File(value);
				System.out.println("new project dir: " + value);
			}

		} catch (IOException e) {
			System.out.println("get user dir error: " + e);
		}

		return dir;
	}

	private File chooseDirectory(String label) {
		JFileChooser chooser = new JFileChooser();

		File userDir = getUserDir();
		chooser.setCurrentDirectory(userDir); // the user's default directory!

		chooser.setDialogTitle("Choose location to " + label + " project");
		chooser.setFileSelectionMode(
			JFileChooser.DIRECTORIES_ONLY);
		chooser.setAcceptAllFileFilterUsed(false);
		chooser.setDialogType(JFileChooser.CUSTOM_DIALOG);
		chooser.setApproveButtonText(label);
		chooser.setFileFilter(new DirectoryFilter());

		// custom change to the Chooser text label "File" :)
		updateLabel(chooser.getComponents(), "Project");

		File selection = null;
		int choice = -1;
		choice = chooser.showDialog(window, label);
		switch (choice) {
			case JFileChooser.APPROVE_OPTION:
				selection = chooser.getSelectedFile();
				break;
			case JFileChooser.CANCEL_OPTION:
				break;
			default:
				JOptionPane.showMessageDialog(window,
					"an old error just occured while " +
					"choosing a directory, please tell Jason");
		}
		return selection;
	}

	private String chooseFilename(String action) {
		String name = JOptionPane.showInputDialog(window,
			"Enter the name of the file to " + action);

		if (name == null) {
			return null;
		}

		if (!name.matches("[\\w]+")) {
			return null;
		}

		return name;
	}

	// custom change to the Chooser text label "File" :)
	// we have to find the label :)
	private void updateLabel(Component[] comps, String str) {
		if (comps == null) {
			return;
		}
		for (Component comp : comps) {
			System.out.println("" + comp);
			if (comp instanceof JPanel) {
				JPanel panel = (JPanel) comp;
				updateLabel(panel.getComponents(), str);
			}
			if (comp instanceof JLabel) {
				JLabel lab = (JLabel) comp;
				if (lab.getText().startsWith("File")) {
					lab.setText(str);
				}
			}
		}
	}

	// filter for directories
	private class DirectoryFilter extends FileFilter {

		public boolean accept(File path) {
			return path.isDirectory();
		}

		public String getDescription() {
			return "Directories only";
		}
	}

	// simple recursive delete of path
	//	if path is a fileMenu, delete the fileMenu
	//	if path is a dir, delete all the elements
	//	inside the dir, then delete the dir
	private void deleteDirectory(File path) {
		if (path.isDirectory()) {
			File[] files = path.listFiles();
			if (files != null) {
				for (File f : files) {
					deleteDirectory(f);
				}
			}
		}
		path.delete();
	}

	private boolean setupDirectory(File dir) {
		boolean created = dir.mkdir();

		if (created) {
			// create template contents of projectMenu
			// subdirectories for source and dist, and compiling
			File src = new File(dir, "src");
			src.mkdir();
			File dist = new File(dir, "dist");
			dist.mkdir();
			File pre = new File(dir, "pre");
			pre.mkdir();
			File classes = new File(dir, "class");
			classes.mkdir();
			File res = new File(dir, "res");
			res.mkdir();
			File pic = new File(res, "pic");
			pic.mkdir();

			// copy the H2O preverified subdir into pre
			Execute.process("System\\runsilent xcopy System\\H2O \"" + pre.getAbsolutePath() + "\\H2O\" /E /I /Y");

			// make the marker file for the project
			try {
				File proj = new File(dir, dir.getName() + ".proj");
				proj.createNewFile();
			} catch (IOException e) {
				System.out.println("createProject error: " + e);
				JOptionPane.showMessageDialog(window, "CRITICAL ERROR! did not create marker file, tell Jason!");
				deleteDirectory(dist);
				return false;
			}
		} else {
			JOptionPane.showMessageDialog(window, "CRITICAL ERROR! did not create project directory, tell Jason!");
			return false;
		}
		return true;
	}

	private boolean verifyDirectory(File dir) {
		// check and see if this directory contains the a project marker file
		if (!dir.exists()) {
			return false;
		}
		if (!dir.isDirectory()) {
			return false;
		}

		File[] files = dir.listFiles(new FilenameFilter() {

			public boolean accept(File dir, String name) {
				return name.equals(dir.getName() + ".proj");
			}
		});

		return files.length == 1; // there should only be one
	}
}
